package isssr.dao.hibernate;

import isssr.dao.exception.DAOException;
import isssr.dao.exception.HibernateDAOException;
import isssr.dao.interfaces.OrganizationalGoalDAO;
import isssr.dao.interfaces.RecordPagination;
import isssr.dao.interfaces.RecordSorting;
import isssr.entity.src.GoalStatus;
import isssr.entity.src.OrganizationalGoal;
import isssr.entity.src.User;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.management.ReflectionException;

import org.hibernate.Criteria;
import org.hibernate.FetchMode;
import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.sql.JoinType;
import org.hibernate.transform.ResultTransformer;
import org.hibernate.transform.Transformers;
import org.hibernate.type.Type;

public class HibernateOrganizationalGoalDAO extends HibernateDAOCommons implements OrganizationalGoalDAO,
		Serializable {
	
	private enum OrgGoalAttributeType {
		UNKNOWN, STRING, NUMBER, USER, GOALSTATUS;
	}

	private String classAliasName;
	private static final Map<String, OrgGoalAttributeType> fieldTypeMap = new HashMap<String, HibernateOrganizationalGoalDAO.OrgGoalAttributeType>();
	
	public HibernateOrganizationalGoalDAO(SessionFactory sessionFactory)
	{
		super(sessionFactory);
		this.classAliasName = "oGoal";
		initFieldMap();
	}

	private void initFieldMap() {
		
		Field[] fields = OrganizationalGoal.class.getDeclaredFields();
		
		String name;
		Class classtype;
		OrgGoalAttributeType type;
		
		for(Field field : fields)
		{
			name = field.getName();
			classtype = field.getType();
			
			if(name.equals("status"))
			{
				type = OrgGoalAttributeType.GOALSTATUS;
			} 
			else if((String.class).isAssignableFrom(classtype))
			{
				type = OrgGoalAttributeType.STRING;
			}
			else if((Number.class).isAssignableFrom(classtype))
			{
				type = OrgGoalAttributeType.NUMBER;
			}
			else if((User.class).isAssignableFrom(classtype))
			{
				type = OrgGoalAttributeType.USER;
			}
			else
			{
				type = OrgGoalAttributeType.UNKNOWN;
			}
			
			fieldTypeMap.put(name, type);
		}
	}



	private void addFilteringToCriteria(Criteria criteria,
			Map<String, Object> filters) throws ReflectiveOperationException {
		
		if(filters == null || filters.isEmpty())
			return;
		
		OrgGoalAttributeType type;
		
		for(Entry<String, Object> entry : filters.entrySet())
		{

			type = fieldTypeMap.get(entry.getKey());
			
			if(type!= null)
			{
				switch(type)
				{
				case STRING:
					addLikeCriterion(criteria, entry);
					break;
				case NUMBER:
					addNumberCriterion(criteria, entry);
					break;
				case USER:
					addUserCriterion(criteria, entry);
				case GOALSTATUS:
					addStatusCriterion(criteria, entry);
					break;
				case UNKNOWN:
					// do nothing
					break;
				}
			}
		}
	}

	private void addStatusCriterion(Criteria criteria,
			Entry<String, Object> parameter) 
	{
		if(parameter.getValue() == null || !(parameter.getValue() instanceof GoalStatus))
			return;
		
		Integer intStatus = null;
		GoalStatus status = (GoalStatus) parameter.getValue();
		
		switch(status)
		{
		case DEFINITION 			: intStatus = 0; break;
		case ELICITATION_VOTE 		: intStatus = 1; break;
		case FEASIBILITY_ANALYSIS	: intStatus = 2; break;
		case DEVELOPEMENT 			: intStatus = 3; break;
		case COMPLETED 				: intStatus = 4; break;
		}
		
		criteria.add(Restrictions.eq(parameter.getKey(), intStatus));
	}

	private void addUserCriterion(Criteria criteria, Entry<String, Object> parameter) throws ReflectiveOperationException {
		
		if(parameter.getValue() == null || !(parameter.getValue() instanceof User))
			return;
		
		User referenceUser = (User)parameter.getValue();
		String username = this.extractUsername(referenceUser);
		
		String label = this.classAliasName + "." + parameter.getKey();
		String alias = "__" + parameter.getKey();
		
		criteria.createAlias(label, alias);
		
		
		Criterion criterion = Restrictions.like(alias + ".username", username, MatchMode.ANYWHERE);
		
		//Criterion criterion = Restrictions.sqlRestriction(" exists ( select * from USER as u, ORGANIZATIONALGOAL as g where USERNAME like '%" + username + "%' and u.uid = ORGANIZATIONALGOAL.GOALENACTOR)");
		criteria.add(criterion);
	}

	private void addNumberCriterion(Criteria criteria,
			Entry<String, Object> parameter) {

		if(parameter.getValue() == null || !(parameter.getValue() instanceof Number))
			return;
		
		criteria.add(Restrictions.eq(parameter.getKey(), (Number)parameter.getValue())); 
		
	}

	private void addLikeCriterion(Criteria criteria, Entry<String, Object> parameter)
	{
		String label = parameter.getKey() ;
		String value = (String) parameter.getValue();
		
		Criterion criterion = Restrictions.like(label, value, MatchMode.ANYWHERE);
		
		criteria.add(criterion);
	}


	private void addOrderingToCriteria(Criteria criteria, RecordSorting ordering) 
	{
		if(ordering == null || ordering.getField() == null)
			return;
		
		switch(ordering.getOrder())
		{
			case ASCENDING: 
				criteria.addOrder(Order.asc(ordering.getField()));
				break;
			case DESCENDING:
				criteria.addOrder(Order.desc(ordering.getField()));
				break;
		}
	}
	
	private void addPagingToCriteria(Criteria criteria, RecordPagination pagination) {

		
		if(pagination == null)
			return;
		
		if(pagination.firstResult != null && pagination.firstResult>0)
			criteria.setFirstResult(pagination.firstResult);
		
		if(pagination.pageSize != null && pagination.pageSize>0)
			criteria.setMaxResults(pagination.pageSize);
		
	}

	private Number countResultsByCriteria(Criteria criteria) {
		
		criteria.setProjection(Projections.rowCount());
		
		Number resultCount = (Number) criteria.uniqueResult();
		
		resetProjectionsInCriteria(criteria);
		
		return resultCount;
	}

	private void resetProjectionsInCriteria(Criteria criteria) {
		criteria.setProjection(null);
		return;
	}
	
	@Override
	public void insert(OrganizationalGoal goal) throws DAOException {

		super.insert(goal);

	}

	@Override
	public void update(OrganizationalGoal goal) throws DAOException {

		super.update(goal);
	}

	@Override
	public void delete(OrganizationalGoal goal) throws DAOException {

		super.delete(goal);
	}

	
	private void setUserCriterion(Criteria criteria, Entry<String, Object> parameter)  {
		if(parameter.getValue() == null || !(parameter.getValue() instanceof User))
			return;
		
		try{
		
		User referenceUser = (User) parameter.getValue();

		String userAlias = setUserAlias(criteria, parameter);
		
		String rhs = extractUsername(referenceUser);
		String lhs = userAlias + ".username";
		
		// may i use the User directly?
		criteria.add(Restrictions.like(lhs,rhs));
		
		}
		catch(ReflectiveOperationException ex)
		{
			ex.printStackTrace();
		}
	}

	private String extractUsername(User referenceUser) throws ReflectiveOperationException {
		
		String username;
		Field field = User.class.getDeclaredField("username");

		field.setAccessible(true);
		username = (String) field.get(referenceUser);
		field.setAccessible(false);
		
		return username;
	}

	private String setUserAlias(Criteria criteria, Entry<String, Object> parameter) {

		String field = parameter.getKey();
		String lhs = this.classAliasName + "." + field;
		String rhs = "_" + field;
		criteria.createAlias(lhs, rhs);

		return rhs;
	}


	private String setUserAlias(Criteria criteria, String fieldName) {
		
		String lhs = this.classAliasName + "." + fieldName;
		String rhs = "_" + fieldName;
		criteria.createAlias(lhs, rhs);
		
		return rhs;
	}
	


	@Override
	public OrganizationalGoal getByTitle(String title) throws DAOException {

		Session session = null;
		
		try
		{
			session = openSession();
			
			Criteria criteria = session.createCriteria(OrganizationalGoal.class, this.classAliasName);
			
			criteria.add(Restrictions.like("title", title, MatchMode.EXACT));
			
			return (OrganizationalGoal) criteria.uniqueResult();
		}
		catch(Exception ex)
		{
			HibernateDAOException daoex = new HibernateDAOException("Error on getByTitle");
			daoex.initCause(ex);
			throw daoex;
		}
		finally
		{
			closeSession(session);
		}
	}

	@Override
	public OrganizationalGoal getByTitleAndOwner(String title, User owner)
			throws DAOException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Integer getAll(List<OrganizationalGoal> resultList)
			throws DAOException {
		
		return getAll(resultList, null, null, null);
	}

	@Override
	public Integer getAll(List<OrganizationalGoal> resultList,
			Map<String, Object> filters, RecordSorting ordering,
			RecordPagination pagination) throws DAOException 
	{
		Session session = null;
		
		try
		{
			session = openSession();
			
			Criteria criteria = session.createCriteria(OrganizationalGoal.class, this.classAliasName);
			
			
			addFilteringToCriteria(criteria, filters);
			
			Number numberOfGoals = countResultsByCriteria(criteria);
			
			addOrderingToCriteria(criteria, ordering);
			addPagingToCriteria(criteria, pagination);
			
			System.out.println("::: " + criteria.toString());
			
			System.out.println(criteria.list());
			
			convertAndCopyList(resultList, criteria.list());
			
			return numberOfGoals.intValue();
		}
		catch(Exception ex)
		{
			String message = super.buildErrorMessage("GetAll");
			HibernateDAOException dex = new HibernateDAOException(message);
			dex.initCause(ex);
			throw dex;
		}
		finally
		{
			closeSession(session);
		}
	}

	private void convertAndCopyList(List<OrganizationalGoal> resultList,
			List criteriaList) {
		
		convertRecordListToGoalList(resultList, criteriaList);
		
	}

	private void convertRecordListToGoalList(
			List<OrganizationalGoal> resultList, List criteriaList) 
	{
		if(criteriaList == null || criteriaList.isEmpty())
			return;
		
		if(isSingleItemRecord(criteriaList.get(0)))
		{
			convertRecordListToGoalList_SingleItemRecord(resultList, criteriaList);
		}
		else
		{
			convertRecordListToGoalList_MultiItemRecord(resultList, criteriaList);
		}
	}

	private boolean isSingleItemRecord(Object object) 
	{
		return (object instanceof Object[] ? false : true);
	}

	private void convertRecordListToGoalList_SingleItemRecord(
			List<OrganizationalGoal> resultList, List criteriaList) 
	{
		Iterator it = criteriaList.iterator();
		Object record;
		while(it.hasNext())
		{
			resultList.add((OrganizationalGoal) it.next());
		}
	}
	

	private void convertRecordListToGoalList_MultiItemRecord(
			List<OrganizationalGoal> resultList, List criteriaList) 
	{
		Iterator it = criteriaList.iterator();
		Object[] record;
		Integer index = 0;
		while(it.hasNext())
		{
			record = (Object[]) it.next();
			while(! record[index].getClass().equals(OrganizationalGoal.class) 
					&& index < record.length)
			{
				index++;
			}
			
			System.out.println("l" + record.length);
			resultList.add((OrganizationalGoal)record[index]);
		}
	}
	
	@Override
	public Integer getAllByOwner(List<OrganizationalGoal> resultList, User owner)
			throws DAOException {
		
		return getAllByOwner(resultList, owner, null, null, null);
	}

	@Override
	public Integer getAllByOwner(List<OrganizationalGoal> resultList,
			User owner, Map<String, Object> filters, RecordSorting ordering,
			RecordPagination pagination) throws DAOException {
		
		Session session = null;
		
		try
		{
			session = openSession();
			
			Criteria criteria = session.createCriteria(OrganizationalGoal.class, this.classAliasName);
			
			addFilteringToCriteria(criteria, filters);
			addOwnerRestrictionToCriteria(criteria, owner);
			
			Number numberOfGoals = countResultsByCriteria(criteria);
			
			addOrderingToCriteria(criteria, ordering);
			addPagingToCriteria(criteria, pagination);

			convertAndCopyList(resultList, criteria.list());
			
			return numberOfGoals.intValue();
		}
		catch(Exception ex)
		{
			String message = super.buildErrorMessage("GetAll");
			HibernateDAOException dex = new HibernateDAOException(message);
			dex.initCause(ex);
			throw dex;
		}
		finally
		{
			closeSession(session);
		}
	}

	private void addOwnerRestrictionToCriteria(Criteria criteria, User owner) 
	{
		//String userAlias = setUserAlias(criteria, "owner");
		
		criteria.add(Restrictions.eq(this.classAliasName + ".goalOwner", owner));	
	}



	@Override
	public Integer getAllBySuper(List<OrganizationalGoal> resultList,
			User superOwner) throws DAOException {
		
		return getAllBySuper(resultList, superOwner, null, null, null);
	}

	@Override
	public Integer getAllBySuper(List<OrganizationalGoal> resultList,
			User superOwner, Map<String, Object> filters,
			RecordSorting ordering, RecordPagination pagination)
			throws DAOException {

		Session session = null;
		
		try
		{
			session = openSession();
			
			Criteria criteria = session.createCriteria(OrganizationalGoal.class, this.classAliasName);
			
			addFilteringToCriteria(criteria, filters);
			addSuperRestrictionToCriteria(criteria, superOwner);
			
			Number numberOfGoals = countResultsByCriteria(criteria);
			
			addOrderingToCriteria(criteria, ordering);
			addPagingToCriteria(criteria, pagination);

			convertAndCopyList(resultList, criteria.list());
			
			return numberOfGoals.intValue();
		}
		catch(Exception ex)
		{
			String message = super.buildErrorMessage("GetAll");
			HibernateDAOException dex = new HibernateDAOException(message);
			dex.initCause(ex);
			throw dex;
		}
		finally
		{
			closeSession(session);
		}
	}

	private void addSuperRestrictionToCriteria(Criteria criteria,
			User superOwner) {

		String proxyAlias = "has_super_table";
		criteria.createAlias(this.classAliasName + ".HAS_SUPER", proxyAlias);
		String superAlias = setUserAlias(criteria, "super_");
		
		Criterion crit1 = Restrictions.sqlRestriction(this.classAliasName + ".superList = " + proxyAlias + ".id");
		Criterion crit2 = Restrictions.sqlRestriction(superAlias + ".uid = " + proxyAlias + ".uid");
		
		criteria.add(crit1);
		criteria.add(crit2);
		
	}



	@Override
	public Integer getAllByEnactor(List<OrganizationalGoal> resultList,
			User enactor) throws DAOException {
		
		return getAllByEnactor(resultList, enactor, null, null, null);
	}

	@Override
	public Integer getAllByEnactor(List<OrganizationalGoal> resultList,
			User enactor, Map<String, Object> filters, RecordSorting ordering,
			RecordPagination pagination) throws DAOException {
		
		Session session = null;
		
		try
		{
			session = openSession();
			
			Criteria criteria = session.createCriteria(OrganizationalGoal.class, this.classAliasName);
			
			addFilteringToCriteria(criteria, filters);
			addEnactorRestrictionToCriteria(criteria, enactor);
			
			Number numberOfGoals = countResultsByCriteria(criteria);
			
			addOrderingToCriteria(criteria, ordering);
			addPagingToCriteria(criteria, pagination);

			convertAndCopyList(resultList, criteria.list());
			
			return numberOfGoals.intValue();
		}
		catch(Exception ex)
		{
			HibernateDAOException daoex = new HibernateDAOException("Error on getByTitle");
			daoex.initCause(ex);
			throw daoex;
		}
		finally
		{
			closeSession(session);
		}
	}



	private void addEnactorRestrictionToCriteria(Criteria criteria, User enactor) {
		
		//String userAlias = setUserAlias(criteria, "enactor");
		
		criteria.add(Restrictions.eq(this.classAliasName + ".goalEnactor", enactor));	
	}

}
